package com.nowcoder.topic.bisection.middle;

import java.util.*;

/**
 * NC349 计算数组的小和
 * @author d3y1
 */
public class NC349{
    /**
     * 代码中的类名、方法名、参数名已经指定，请勿修改，直接返回方法规定的值即可
     *
     *
     * @param nums int整型ArrayList
     * @return long长整型
     */
    public long calArray (ArrayList<Integer> nums) {
//        return solution1(nums);
        return solution2(nums);
//        return solution3(nums);
    }

    /**
     * 二分: 超时!
     * @param nums
     * @return
     */
    private long solution1(ArrayList<Integer> nums){
        long result = 0;
        int n = nums.size();

        ArrayList<Integer> list = new ArrayList<>();
        list.add(nums.get(0));

        // pos: 当前num(target)插入list位置(索引)
        int left,right,mid,last,target,pos,sum;
        for(int i=1; i<n; i++){
            last = list.get(i-1);
            target = nums.get(i);
            sum = 0;
            // 直接附加
            if(last <= target){
                pos = i;
                list.add(pos, target);
            }
            // 二分查找
            else{
                left = 0;
                right = i-1;
                while(left <= right){
                    mid = (left+right)>>1;
                    if(list.get(mid) <= target){
                        left = mid+1;
                    }else if(list.get(mid) > target){
                        right = mid-1;
                    }
                }

                pos = left;
                list.add(pos, target);
            }

            // 求和(小于等于当前num(target))
            for(int j=0; j<pos; j++){
                sum += list.get(j);
            }

            result += sum;
        }

        return result;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * 树状数组(Binary Indexed Tree)(也称Fenwick树): 动态维护前缀和
     *
     * 参考: https://www.bilibili.com/video/BV1pE41197Qj/?vd_source=fa6990445a0e68c4b18e85e07647ab23
     *
     * @param nums
     * @return
     */
    private long solution2(ArrayList<Integer> nums){
        long result = 0;
        int n = nums.size();

        // 离散化处理
        discretization(nums);

        // 初始化
        init(nums.size()+1);

        int num;
        for(int i=0; i<n; i++){
            num = nums.get(i);
            // 树状数组id
            int id = getId(num);
            // 根据id获取前缀和
            result += query(id);
            // 更新前缀和
            update(id, num);
        }

        return result;
    }

    // 树状数组t
    private int[] t;
    // 将nums离散化, 去重+排序, 转化为数组a
    private int[] a;

    /**
     * 初始化
     * @param length
     */
    private void init(int length) {
        t = new int[length];
        Arrays.fill(t, 0);
    }

    /**
     * 非负整数n在二进制表示下最低位1及后面的0所构成的数值
     *
     * 示例:
     * lowBit(44) = lowBit(101100[2进制]) = 100[2进制] = 4[10进制]
     *
     *         n    101100
     * 取反:   ~n    010011
     * 取反+1: ~n+1  010100
     *
     * ~n+1 = -n (计算机存储使用补码, 取反加1后的值即为负的这个数)
     *
     * lowBit(44) = n & (~n+1) = n & (-n)
     *
     * @param x
     * @return
     */
    private int lowBit(int x) {
        return x & (-x);
    }

    /**
     * 更新前缀和: 从当前节点往树根逐层更新父节点
     * @param pos
     * @param num
     */
    private void update(int pos, int num) {
        while (pos < t.length) {
            t[pos] += num;
            // 当前节点的父节点
            pos += lowBit(pos);
        }
    }

    /**
     * 查询前缀和: 从当前节点往左上逐个累加节点值
     * @param pos
     * @return
     */
    private long query(int pos) {
        long ret = 0;
        while (pos > 0) {
            ret += t[pos];
            // 当前节点的左上节点
            pos -= lowBit(pos);
        }

        return ret;
    }

    /**
     * 离散化处理
     *
     * nums数组中可能有负数或者可能稀疏 -> 离散化处理
     *
     * @param nums
     */
    private void discretization(ArrayList<Integer> nums) {
        Set<Integer> set = new HashSet<>(nums);
        int size = set.size();
        a = new int[size];
        int index = 0;
        for (int num : set) {
            a[index++] = num;
        }
        Arrays.sort(a);
    }

    /**
     * 根据num获取树状数组id: num -> id
     * @param num
     * @return
     */
    private int getId(int num) {
        return Arrays.binarySearch(a, num)+1;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * 归并
     *
     * nums[i]最终累加次数 等价于 后面有多少个大于等于nums[i]的数(count[i])
     *
     * 示例:
     *   i   0 1 2 3 4 5
     * nums  1 3 5 2 4 6
     * count 5 3 1 2 1 0
     *
     * result = 1*5 + 3*3 + 5*1 + 2*2 + 4*1
     *        = 5 + 9 + 5 + 4 + 4
     *        = 27
     *
     * @param nums
     * @return
     */
    private long solution3(ArrayList<Integer> nums){
        Integer[] numsArr = new Integer[nums.size()];
        nums.toArray(numsArr);
        return mergeSort(numsArr, 0, nums.size()-1);
    }

    /**
     * 归并排序
     * @param nums
     * @param left
     * @param right
     * @return
     */
    private long mergeSort(Integer[] nums, int left, int right) {
        if(left >= right) {
            return 0;
        }

        int mid = left+((right-left)>>1);

        return mergeSort(nums, left, mid)+mergeSort(nums, mid+1, right)+merge(nums, left, mid, right);
    }

    /**
     * 合并
     * @param nums
     * @param left
     * @param mid
     * @param right
     * @return
     */
    private long merge(Integer[] nums, int left, int mid, int right) {
        long result = 0L;
        int[] help = new int[right-left+1];
        int i = 0;
        int p = left;
        int q = mid+1;
        while(p<=mid && q<=right) {
            result += (nums[p]<=nums[q]) ? nums[p]*(right-q+1) : 0;
            help[i++] = (nums[p]<=nums[q]) ? nums[p++] : nums[q++];
        }
        while(p <= mid) {
            help[i++] = nums[p++];
        }
        while(q <= right) {
            help[i++] = nums[q++];
        }
        for(i=0; i<help.length; i++) {
            nums[left+i] = help[i];
        }

        return result;
    }
}